<?php


namespace App\Services;


use App\Constants\MiniProgramConstant;
use App\Constants\PayTypeConstant;
use App\Constants\TemplateMessageConstant;
use App\Constants\VipLogOperateTypeConstant;
use App\Constants\VipOrderStatusConstant;
use App\Exceptions\BusinessException;
use App\Jobs\Message\SendMessage;
use App\Models\Member;
use App\Models\MemberVipLog;
use App\Models\MemberVipOrder;
use App\Models\PayLog;
use App\Models\Vip;
use Illuminate\Contracts\Cache\LockTimeoutException;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class PayService
{
    public function unifiedOrder(int $memberId, string $miniType, int $vipId)
    {
        $lock = Cache::lock('vip_unified_order_' . $memberId, 10);

        try {
            DB::beginTransaction();

            $lock->block(5);

            $vip = Vip::find($vipId);
            if (!$vip) throw new BusinessException('vip信息不存在');

            $member = Member::find($memberId);
            if (!$member) throw new BusinessException('会员信息不存在');

            $orderSn = generateUniqueSn();
            $paySn = generateUniqueSn();

            $payConfig = get_wechat_pay_config($miniType);
            $payConfig['notify_url'] = ngrokUrl('wechat_notify.vip_pay', ['sn' => $paySn]);

            if (!$payConfig['app_id'] || !$payConfig['mch_id'] || !$payConfig['key']) {
                throw new BusinessException('微信支付配置不能为空');
            }

            MemberVipOrder::create([
                'member_id'  => $memberId,
                'mini_type'  => $miniType,
                'order_sn'   => $orderSn,
                'pay_sn'     => $paySn,
                'pay_amount' => $vip->price,
                'pay_time'   => 0,
                'vip_id'     => $vipId,
                'vip_price'  => $vip->price,
                'vip_day'    => $vip->day,
            ]);

            PayLog::create([
                'mini_type'  => $miniType,
                'type'       => PayTypeConstant::TYPE_BY_BUY_VIP,
                'pay_sn'     => $paySn,
                'pay_amount' => $vip->price,
                'pay_type'   => 'wechat',
                'is_pay'     => false,
                'pay_time'   => 0,
                'extend'     => json_encode($payConfig)
            ]);

            $wechatPaymentService = new WechatPaymentService($payConfig);
            $ret = $wechatPaymentService->unifiedOrder([
                'body'         => sprintf('购买%d天vip', $vip->day),
                'out_trade_no' => $paySn,
                'total_fee'    => $vip->price,
                'openid'       => $member->open_id,
                'trade_type'   => 'JSAPI',
            ]);

            Log::debug(sprintf('支付单号【%s】微信支付响应结果', $paySn), $ret);

            if ($ret['return_code'] != 'SUCCESS') {
                throw new BusinessException($ret['return_msg'] ?? '获取微信配支付置信息失败');
            }

            if ($ret['result_code'] != 'SUCCESS') {
                throw new BusinessException($ret['err_code_des'] ?? '获取微信支付配置信息失败');
            }

            $result = $wechatPaymentService->getSdkPayConfig($ret['prepay_id']);
            if (!$result) {
                throw new BusinessException('获取微信支付配置信息失败');
            }

            DB::commit();

            return $result;
        } catch (LockTimeoutException $le) {
            DB::rollBack();
            throw new BusinessException('操作频繁,请稍后再试');
        } catch (BusinessException $be) {
            DB::rollBack();
            throw $be;
        } catch (\Throwable $e) {
            DB::rollBack();
            log_exception('获取微信小程序支付配置异常', $e);
            throw new BusinessException('获取支付配置失败');
        } finally {
            optional($lock)->release();
        }
    }

    public function paidNotify(string $paySn)
    {
        $log = PayLog::where('pay_sn', $paySn)->first();
        if (!$log) {
            return response(<<<XML
<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>
XML
                , 200, ['Content-Type' => 'text/xml']);
        }

        $config = json_decode($log->extend, true);
        $wechatPaymentService = new WechatPaymentService($config);

        return $wechatPaymentService->paidNotify($paySn, function ($paySn, $tradeNo) {
            return $this->paidHandel($paySn, $tradeNo);
        });
    }

    public function paidHandel(string $paySn, string $tradeNo): bool
    {
        try {
            DB::beginTransaction();

            $order = MemberVipOrder::where('pay_sn', $paySn)->first();
            $member = Member::find($order->member_id);
            $payLog = PayLog::where('pay_sn', $paySn)->first();

            if (!$order) {
                throw new BusinessException(sprintf('支付单号【%s】 订单不存在', $paySn));
            }
            if ($order->status != VipOrderStatusConstant::WAIT_PAY) {
                throw new BusinessException(sprintf('支付单号为【%s】 订单状态不为未支付', $paySn));
            }
            if (!$payLog) {
                throw new BusinessException(sprintf('支付单号为【%s】 订单不存在', $paySn));
            }

            $startTime = $member->is_vip ? $member->vip_end_time : time();
            $endTime = ($order->vip_day * 24 * 3600) + $startTime;

            $member->update(['is_vip' => 1, 'vip_end_time' => $endTime]);
            $order->update([
                'pay_time'   => time(),
                'status'     => VipOrderStatusConstant::IS_PAY,
                'start_time' => $startTime,
                'end_time'   => $endTime,
            ]);
            $payLog->update([
                'is_pay'      => true,
                'trade_no'    => $tradeNo,
                'pay_time'    => time(),
                'notify_time' => time()
            ]);

            MemberVipLog::create([
                'member_id'    => $order->member_id,
                'operate_type' => VipLogOperateTypeConstant::PAY,
                'content'      => sprintf('购买%d天vip', $order->vip_day),
            ]);

            SendMessage::dispatch($order->member_id, TemplateMessageConstant::VIP_OPEN_NOTICE, [
                'open_vip_date' => date('Y-m-d H:i:s')
            ]);


            DB::commit();
        } catch (BusinessException $e) {
            DB::rollBack();
            Log::error($e->getMessage());
            return false;
        } catch (\Throwable $e) {
            DB::rollBack();
            log_exception(sprintf('支付单号【%s】 订单更改状态异常', $paySn), $e);
            return false;
        }

        return true;
    }
}
